/*
 * SPDX-FileCopyrightText: 2013 Freie Universität Berlin
 * SPDX-License-Identifier: LGPL-2.1-only
 */

/**
 * @ingroup     core_thread
 * @{
 *
 * @file
 * @brief       Threading implementation
 *
 * @author      Kaspar Schleiser <kaspar@schleiser.de>
 *
 * @}
 */

#include <errno.h>
#include <stdalign.h>
#include <stdio.h>
#ifdef PICOLIBC_TLS
#include <picotls.h>
#endif

#include "assert.h"
#include "thread.h"
#include "irq.h"

#include "bitarithm.h"
#include "sched.h"

#define ENABLE_DEBUG 0
#include "debug.h"

#if defined(HAVE_VALGRIND_H)
#  include <valgrind.h>
#elif defined(HAVE_VALGRIND_VALGRIND_H)
#  include <valgrind/valgrind.h>
#else
#  define   VALGRIND_DISABLE_ERROR_REPORTING    (void)0
#  define   VALGRIND_ENABLE_ERROR_REPORTING     (void)0
#endif

thread_status_t thread_getstatus(kernel_pid_t pid)
{
    thread_t *thread = thread_get(pid);

    return thread ? thread->status : STATUS_NOT_FOUND;
}

const char *thread_getname(kernel_pid_t pid)
{
#ifdef CONFIG_THREAD_NAMES
    thread_t *thread = thread_get(pid);
    return thread ? thread->name : NULL;
#else
    (void)pid;
    return NULL;
#endif
}

void thread_zombify(void)
{
    if (irq_is_in()) {
        return;
    }

    irq_disable();
    sched_set_status(thread_get_active(), STATUS_ZOMBIE);
    irq_enable();
    thread_yield_higher();

    /* this line should never be reached */
    UNREACHABLE();
}

int thread_kill_zombie(kernel_pid_t pid)
{
    DEBUG("thread_kill: Trying to kill PID %" PRIkernel_pid "...\n", pid);
    unsigned state = irq_disable();

    int result = (int)STATUS_NOT_FOUND;

    thread_t *thread = thread_get(pid);

    if (!thread) {
        DEBUG("thread_kill: Thread does not exist!\n");
    }
    else if (thread->status == STATUS_ZOMBIE) {
        DEBUG("thread_kill: Thread is a zombie.\n");

        sched_threads[pid] = NULL;
        sched_num_threads--;
        sched_set_status(thread, STATUS_STOPPED);

        result =  1;
    }
    else {
        DEBUG("thread_kill: Thread is not a zombie!\n");
    }
    irq_restore(state);
    return result;
}

void thread_sleep(void)
{
    if (irq_is_in()) {
        return;
    }

    unsigned state = irq_disable();

    sched_set_status(thread_get_active(), STATUS_SLEEPING);
    irq_restore(state);
    thread_yield_higher();
}

int thread_wakeup(kernel_pid_t pid)
{
    DEBUG("thread_wakeup: Trying to wakeup PID %" PRIkernel_pid "...\n", pid);

    unsigned old_state = irq_disable();

    thread_t *thread = thread_get(pid);

    if (!thread) {
        DEBUG("thread_wakeup: Thread does not exist!\n");
    }
    else if (thread->status == STATUS_SLEEPING) {
        DEBUG("thread_wakeup: Thread is sleeping.\n");

        sched_set_status(thread, STATUS_PENDING);

        irq_restore(old_state);
        sched_switch(thread->priority);

        return 1;
    }
    else {
        DEBUG("thread_wakeup: Thread is not sleeping!\n");
    }

    irq_restore(old_state);
    return (int)STATUS_NOT_FOUND;
}

void thread_yield(void)
{
    unsigned old_state = irq_disable();
    thread_t *me = thread_get_active();

    if (me->status >= STATUS_ON_RUNQUEUE) {
        sched_runq_advance(me->priority);
    }
    irq_restore(old_state);

    thread_yield_higher();
}

/*
 * Example of insertion operations in the linked list
 *   thread_add_to_list(list,new_node) //Prio4
 *   list         list      new_node
 *  +------+     +------+   +------+
 *  |    + | ->  |    + |   | 4  + |
 *  +----|-+     +----|-+   +----|-+
 *       +-->NULL     +-----/\   +-->NULL
 *  thread_add_to_list(list,higher_node) //Prio2(Higher)
 *  list       new_node   higher_node
 *  +------+   +------+   +------+
 *  |    + |   | 4  + |   | 2  + |
 *  +----|-+   +----|-+   +----|-+
 *       |          +-->NULL   |
 *       |      /\-------------+
 *       +----------------/\
 *  thread_add_to_list(list,lower_node) //Prio6(Lower)
 *  list       new_node   lower_node
 *  +------+   +------+   +------+
 *  |    + |   | 4  + |   | 6  + |
 *  +----|-+   +----|-+   +----|-+
 *       +-----/\   +-----/\   +-->NULL
 */
void thread_add_to_list(list_node_t *list, thread_t *thread)
{
    assert(thread->status < STATUS_ON_RUNQUEUE);

    uint16_t my_prio = thread->priority;
    list_node_t *new_node = (list_node_t *)&thread->rq_entry;

    while (list->next) {
        thread_t *list_entry = container_of((clist_node_t *)list->next,
                                            thread_t, rq_entry);
        if (list_entry->priority > my_prio) {
            break;
        }
        list = list->next;
    }

    new_node->next = list->next;
    list->next = new_node;
}

uintptr_t measure_stack_free_internal(const char *stack, size_t size)
{
    /* Alignment of stack has been fixed (if needed) by thread_create(), so
     * we can silence -Wcast-align here */
    uintptr_t *stackp = (uintptr_t *)(uintptr_t)stack;
    uintptr_t end = (uintptr_t)stack + size;

    /* HACK: This will affect native32/native64 only.
     *
     * The dark magic used here is frowned upon by valgrind. E.g. valgrind may
     * deduce that a specific value was at some point allocated on the stack,
     * but has gone out of scope. When that value is now read again to
     * estimate stack usage, it does look a lot like someone passed a pointer
     * to a stack allocated value, and that pointer is referenced after that
     * value has gone out of scope. */
    VALGRIND_DISABLE_ERROR_REPORTING;

    /* assume that the stack grows "downwards" */
    while (((uintptr_t)stackp < end) && (*stackp == (uintptr_t)stackp)) {
        stackp++;
    }

    VALGRIND_ENABLE_ERROR_REPORTING;

    uintptr_t space_free = (uintptr_t)stackp - (uintptr_t)stack;

    return space_free;
}

kernel_pid_t thread_create(char *stack, int stacksize, uint8_t priority,
                           int flags, thread_task_func_t function, void *arg,
                           const char *name)
{
    if (priority >= SCHED_PRIO_LEVELS) {
        return -EINVAL;
    }

#ifdef DEVELHELP
    int total_stacksize = stacksize;
#endif
#ifndef CONFIG_THREAD_NAMES
    (void)name;
#endif

    /* align the stack on a 16/32bit boundary */
    uintptr_t misalignment = (uintptr_t)stack % alignof(void *);
    if (misalignment) {
        misalignment = alignof(void *) - misalignment;
        stack += misalignment;
        stacksize -= misalignment;
    }

    /* make room for the thread control block */
    stacksize -= sizeof(thread_t);

    /* round down the stacksize to a multiple of thread_t alignments (usually 16/32bit) */
    stacksize -= stacksize % alignof(thread_t);

    if (stacksize < 0) {
        DEBUG("thread_create: stacksize is too small!\n");
        return -EINVAL;
    }
    /* allocate our thread control block at the top of our stackspace. Cast to
     * (uintptr_t) intermediately to silence -Wcast-align. (We manually made
     * sure alignment is correct above.) */
    thread_t *thread = (thread_t *)(uintptr_t)(stack + stacksize);

#ifdef PICOLIBC_TLS
#if __PICOLIBC_MAJOR__ > 1 || __PICOLIBC_MINOR__ >= 8
#define TLS_ALIGN       (alignof(thread_t) > _tls_align() ? alignof(thread_t) : _tls_align())
#else
#define TLS_ALIGN       alignof(thread_t)
#endif
    char *tls = stack + stacksize - _tls_size();
    /*
     * Make sure the TLS area is aligned as required and that the
     * resulting stack will also be aligned as required
     */
    thread->tls = (void *) ((uintptr_t) tls & ~ (TLS_ALIGN - 1));
    stacksize = (char *) thread->tls - stack;

    _init_tls(thread->tls);
#endif

#if defined(DEVELHELP) || defined(SCHED_TEST_STACK) \
    || defined(MODULE_TEST_UTILS_PRINT_STACK_USAGE)
    if (flags & THREAD_CREATE_NO_STACKTEST) {
        /* create stack guard. Alignment has been handled above, so silence
         * -Wcast-align */
        *(uintptr_t *)(uintptr_t)stack = (uintptr_t)stack;
    }
    else {
        /* assign each int of the stack the value of it's address. Alignment
         * has been handled above, so silence -Wcast-align */
        uintptr_t *stackmax = (uintptr_t *)(uintptr_t)(stack + stacksize);
        uintptr_t *stackp = (uintptr_t *)(uintptr_t)stack;

        while (stackp < stackmax) {
            *stackp = (uintptr_t)stackp;
            stackp++;
        }
    }
#endif

    unsigned state = irq_disable();

    kernel_pid_t pid = KERNEL_PID_UNDEF;
    for (kernel_pid_t i = KERNEL_PID_FIRST; i <= KERNEL_PID_LAST; ++i) {
        if (sched_threads[i] == NULL) {
            pid = i;
            break;
        }
    }
    if (pid == KERNEL_PID_UNDEF) {
        DEBUG("thread_create(): too many threads!\n");

        irq_restore(state);

        return -EOVERFLOW;
    }

    sched_threads[pid] = thread;

    thread->pid = pid;
    thread->sp = thread_stack_init(function, arg, stack, stacksize);

#if defined(DEVELHELP) || IS_ACTIVE(SCHED_TEST_STACK) || \
    defined(MODULE_MPU_STACK_GUARD) || defined(MODULE_CORTEXM_STACK_LIMIT)
    thread->stack_start = stack;
#endif

#ifdef DEVELHELP
    thread->stack_size = total_stacksize;
#endif
#ifdef CONFIG_THREAD_NAMES
    thread->name = name;
#endif

    thread->priority = priority;
    thread->status = STATUS_STOPPED;

    thread->rq_entry.next = NULL;

#ifdef MODULE_CORE_MSG
    thread->wait_data = NULL;
    thread->msg_waiters.next = NULL;
    cib_init(&(thread->msg_queue), 0);
    thread->msg_array = NULL;
#endif

    sched_num_threads++;

    DEBUG("Created thread %s. PID: %" PRIkernel_pid ". Priority: %u.\n", name,
          thread->pid, priority);

    if (flags & THREAD_CREATE_SLEEPING) {
        sched_set_status(thread, STATUS_SLEEPING);
    }
    else {
        sched_set_status(thread, STATUS_PENDING);

        if (!(flags & THREAD_CREATE_WOUT_YIELD)) {
            irq_restore(state);
            sched_switch(priority);
            return pid;
        }
    }

    irq_restore(state);

    return pid;
}

static const char *state_names[STATUS_NUMOF] = {
    [STATUS_STOPPED] = "stopped",
    [STATUS_ZOMBIE] = "zombie",
    [STATUS_SLEEPING] = "sleeping",
    [STATUS_MUTEX_BLOCKED] = "bl mutex",
    [STATUS_RECEIVE_BLOCKED] = "bl rx",
    [STATUS_SEND_BLOCKED] = "bl send",
    [STATUS_REPLY_BLOCKED] = "bl reply",
    [STATUS_FLAG_BLOCKED_ANY] = "bl anyfl",
    [STATUS_FLAG_BLOCKED_ALL] = "bl allfl",
    [STATUS_MBOX_BLOCKED] = "bl mbox",
    [STATUS_COND_BLOCKED] = "bl cond",
    [STATUS_RUNNING] = "running",
    [STATUS_PENDING] = "pending",
};

#define STATE_NAME_UNKNOWN "unknown"

const char *thread_state_to_string(thread_status_t state)
{
    const char *name =  NULL;
    if (state < STATUS_NUMOF) {
        name = state_names[state];
    }

    /* if compiling with assertions, this is an error
     * that indicates that the table above is incomplete */
    assert(name != NULL);

    return (name != NULL) ? name : STATE_NAME_UNKNOWN;
}
